package server;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.security.KeyStore;

import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.security.cert.X509Certificate;

/**
 * Starts the server and establishes connections for the threads.
 */
public class Server {
	public final static int IDLE_COMMAND = 0;
	protected final String eMessage = "Wrong number of args: " + 
	"run as java Server <port>";
	protected final String eMessage2 = "Must use format: " + 
	"<int port = (>1024)> ie: java Server 1337";
	public final static String serverstore = "src/keystores/serverstore";
	public final String truststore = "src/keystores/truststore_s";
	
	private int port;

	/**
	 * Main method for the server.
	 * 
	 * Starts and initiates the communication socket for one camera.
	 * @param args the command line arguments.
	 */
	public static void main(String args[]) {
		Server cs = new Server();

		cs.mainMethod(args);
		//cs.loadServer(args[1], args[2]);
	}
	
	/**
	 * Starts part of the server. DO NOT USE !!!  
	 * DO NOT USE !!! Always call {@link #main(String[])} instead
	 */
	public Server(){
		System.setProperty("javax.net.ssl.keyStore", serverstore);
		System.setProperty("javax.net.ssl.keyStorePassword", "medical");
		System.setProperty("javax.net.ssl.trustStore", truststore);
		System.setProperty("javax.net.ssl.trustStorePassword", "medical");
		port = 0;
	}
	
	/**
	 * Runs the server.
	 * Accepts connections and calls the parser for the command line arguments.
	 * @param args the command line arguments
	 */
	private void mainMethod(String[] args){
		handleArgs(args);
		ServerSocket serversocket = null;
		try {
			ServerSocketFactory ssf = getServerSocketFactory("TLS");
			serversocket = ssf.createServerSocket(port);
			((SSLServerSocket)serversocket).setNeedClientAuth(true);
				    
			System.out.println("ServerSocket opened at port " + port);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		while (true) { 
			SSLSocket socket = null;

			// accept a connection
			try {
			    socket = (SSLSocket)serversocket.accept();
			    SSLSession session = socket.getSession();
			    X509Certificate cert = (X509Certificate)session.getPeerCertificateChain()[0];
				
				Monitor monitor = new Monitor();
				String[] loginIfo = cert.getSubjectDN().getName().split("=");
				monitor.setLogin(loginIfo[1].split(",")[0], loginIfo[2].split(",")[0]);
				doReadThread(socket, monitor);
				doServerThread(socket, monitor);
			} catch (IOException e) {
				e.printStackTrace();
			} 
		}
	}
	
	/**
	 * Parses the command line arguments.
	 * @param args the command line arguments
	 */
	private void handleArgs(String[] args){
		if (args.length != 1) {
			System.err.println(eMessage);
			System.exit(-1);
		} else {
			try {
				port = Integer.parseInt(args[0]);
			} catch (NumberFormatException e) {
				e.printStackTrace();
				System.err.println(eMessage2);
				System.exit(-1);
			}
		}
	}

	private static ServerSocketFactory getServerSocketFactory(String type) {
	if (type.equals("TLS")) {
	    SSLServerSocketFactory ssf = null;
	    try {
		// set up key manager to do server authentication
		SSLContext ctx;
		KeyManagerFactory kmf;
		KeyStore ks;
		char[] passphrase = "medical".toCharArray();
	
		ctx = SSLContext.getInstance("TLS");
		kmf = KeyManagerFactory.getInstance("SunX509");
		ks = KeyStore.getInstance("JKS");
	
		ks.load(new FileInputStream(serverstore), passphrase);
		kmf.init(ks, passphrase);
		ctx.init(kmf.getKeyManagers(), null, null);
	
		ssf = ctx.getServerSocketFactory();
		return ssf;
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	} else {
	    return ServerSocketFactory.getDefault();
	}
	return null;
	}

	/**
	 * Starts the command handler.
	 * @param socket		The socket to receive commands from
	 * @param monitor		The {@link server.Monitor monitor} for the whole server.
	 * @throws IOException	if an I/O error occurs when creating the input stream, the socket is closed, the socket is not connected, or the socket input has been shutdown using {@link java.net.Socket#getInputStream() socket.shutdownInput()}
	 */
	private void doReadThread(SSLSocket socket, server.Monitor monitor)	throws IOException {
		new ReadCommands(socket.getInputStream(), monitor).start();
	}
	
	/**
	 * Starts the {@link server.ServerThread server} thread.
	 * @param socket the socket to send the documents through
	 * @param monitor the {@link server.Monitor monitor} for the whole server
	 */
	private void doServerThread(SSLSocket socket, server.Monitor monitor) {
		new ServerThread(socket, monitor).start();
	}
}

